package com.drakulo.games.ais.core.io;

import java.io.File;
import java.io.FileOutputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

import com.drakulo.games.ais.core.Resource;
import com.drakulo.games.ais.core.building.BuildingData;
import com.drakulo.games.ais.core.building.BuildingLevelData;
import com.drakulo.games.ais.core.building.BuildingType;
import com.drakulo.games.ais.core.delayed.SpecialActionManager;

public class BuildingIO {
	private static final String FILE_EXT = ".xml";

	private static Map<String, BuildingData> buildingDatas = new HashMap<String, BuildingData>();

	public static void loadBuildingsData() throws AisIOException {
		File folder = new File("./data/buildings");
		String[] configs = folder.list();
		File currentFile;
		BuildingData currentData;
		for (String confName : configs) {
			if (!confName.endsWith(FILE_EXT)) {
				// Ignore non config files
				continue;
			}
			currentFile = new File("./data/buildings/" + confName);
			currentData = load(currentFile);
			buildingDatas.put(confName, currentData);
		}
	}

	/**
	 * Retreive building data from cache
	 * 
	 * @param type
	 *            the building type
	 * @return the building data
	 */
	public static BuildingData getBuildingData(BuildingType type) {
		return buildingDatas.get(type.toString() + FILE_EXT);
	}

	public static void save(String fileName, BuildingData data) {
		Element levels = new Element("levels");

		Map<Integer, BuildingLevelData> levelsData = data.getLevelData();
		Set<Integer> keys = levelsData.keySet();
		for (Integer key : keys) {
			levels.addContent(handleLevelData(levelsData.get(key), key));
		}

		Element root = new Element("building");
		root.addContent(levels);

		XMLOutputter output = new XMLOutputter(Format.getPrettyFormat());
		try {
			output.output(new Document(root), new FileOutputStream(fileName
					+ FILE_EXT));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	public static BuildingData load(File f) throws AisIOException {
		SAXBuilder sxb = new SAXBuilder();
		try {
			Document document = sxb.build(f);
			Element root = document.getRootElement();
			BuildingData data = new BuildingData();
			List<Element> levels = root.getChild("levels").getChildren("level");
			for (Element levelElement : levels) {
				String value = levelElement.getAttributeValue("value");
				Integer level = "".equals(value) ? 0 : Integer.valueOf(value);
				data.putData(level, buildLevelData(levelElement));
			}

			return data;
		} catch (NumberFormatException e) {
			throw new AisIOException("Invalid file format for : " + f.getName());
		} catch (Exception e) {
			e.printStackTrace();
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	private static BuildingLevelData buildLevelData(Element root) {
		BuildingLevelData levelData = new BuildingLevelData();

		// Production
		Element production = root.getChild("production");
		levelData.setProduction(buildResourceMap(production));

		// Consumption
		Element consumption = root.getChild("consumption");
		levelData.setConsumption(buildResourceMap(consumption));

		// Store
		Element store = root.getChild("store");
		levelData.setStoreCapacity(buildResourceMap(store));

		// Upgrade
		Element upgrade = root.getChild("upgrade");
		levelData.setUpgradeCost(buildResourceMap(upgrade));

		// Technologies needed to upgrade
		Element techs = root.getChild("technologies");
		if (techs == null) {
			levelData.setTechnologiesNeeded(null);
		} else {
			List<String> technologies = new ArrayList<String>();
			List<Element> children = techs.getChildren("technology");
			for (Element elem : children) {
				technologies.add(elem.getText());
			}
			levelData.setTechnologiesNeeded(technologies);
		}

		// Special actions
		Element actions = root.getChild("special");
		if (actions == null) {
			levelData.setSpecialActions(null);
		} else {
			List<String> actionList = new ArrayList<String>();
			List<Element> children = actions.getChildren("action");
			for (Element elem : children) {
				// Create the special action
				String name = elem.getChildText("name");
				int duration = Integer.valueOf(elem.getChildText("duration"))
						.intValue();
				Map<Resource, BigDecimal> costMap = buildResourceMap(elem
						.getChild("cost"));
				String gfxKey = elem.getChildText("gfx");
				String i18nKey = elem.getChildText("i18n");
				SpecialActionManager.create(name, duration, costMap, gfxKey,
						i18nKey);

				actionList.add(name);
			}
			levelData.setSpecialActions(actionList);
		}

		return levelData;
	}

	private static Map<Resource, BigDecimal> buildResourceMap(Element root) {
		Map<Resource, BigDecimal> map = new HashMap<Resource, BigDecimal>();

		Resource[] resources = Resource.values();
		for (Resource r : resources) {
			Element e = root.getChild(r.toString().toLowerCase());
			if (e == null) {
				map.put(r, BigDecimal.ZERO);
			} else {
				map.put(r, BigDecimal.valueOf(Long.valueOf(e.getText())));
			}
		}

		return map;
	}

	private static Element handleLevelData(BuildingLevelData data, Integer level) {
		Element levelElement = new Element("level");
		levelElement.setAttribute("value", String.valueOf(level));

		Element production = produceResourceNode(data.getProduction(),
				"production");
		Element consumption = produceResourceNode(data.getConsumption(),
				"consumption");
		Element storeCapacity = produceResourceNode(data.getStoreCapacity(),
				"store");
		Element upgrade = produceResourceNode(data.getUpgradeCost(), "upgrade");

		levelElement.addContent(production);
		levelElement.addContent(consumption);
		levelElement.addContent(storeCapacity);
		levelElement.addContent(upgrade);

		return levelElement;
	}

	private static Element produceResourceNode(Map<Resource, BigDecimal> data,
			String containerName) {
		Element container = new Element(containerName);
		for (Resource r : data.keySet()) {
			Element item = new Element(r.toString().toLowerCase());
			item.setText(String.valueOf(data.get(r)));
			container.addContent(item);
		}
		return container;
	}
}
